import os
import time
import sys
from time import sleep, strftime
import requests
from concurrent import futures

POP20_CC = ('CN IN US ID BR PK NG BD RU JP '
            'MX PH VN ET EG DE IR TR CD FR').split()
BASE_URL = 'http://flupy.org/data/flags'

BASE_DIR = 'downloads/'

MAX_WORKERS = 20


def save_flag(img, filename: str):
    path = os.path.join(BASE_URL, filename)
    with open(path, 'wb') as fp:
        fp.write(img)


def get_flag(cc: str):
    url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
    resp = requests.get(url)
    return resp.content


def show(text):
    print(text, end=' ')
    sys.stdout.flush()


def download_one(cc: str):
    image = get_flag(cc)
    show(cc)
    save_flag(image, cc.lower() + '.gif')


def download_many(cc_list: list):
    for cc in sorted(cc_list):
        download_one(cc)
    return len(cc_list)


def main():
    t0 = time.time()
    count = download_many(POP20_CC)
    elasped = time.time() - t0
    msg = '\n{} flags downloaded in {:.2f}s'
    print(msg.format(count, elasped))


def download_many_with_thread(cc_list):
    workers = min(MAX_WORKERS, len(cc_list))
    # 这个并不是真的多线程, 因为GIL的存在
    # 这里要注意, Python在执行阻塞型I/O操作的时候, 阻塞型I/O函数会释放GIL, 比如此处在写入图片和下载时
    # 标准库中每个使用C语编写的I/O函数都会释放GIL, 因此, 当某个线程在等待I/O时,
    # Python调度器会切换到另一线程
    with futures.ThreadPoolExecutor(workers) as executor:
        res = executor.map(download_one, sorted(cc_list))
    return len(list(res))

def download_many_with_process(cc_list):
    # 使用进程实现真正的并行
    with futures.ProcessPoolExecutor() as executor:
        res = executor.map(download_one, sorted(cc_list))
    return len(list(res))


def download_many_with_futures(cc_list):
    """
    download_many_with_futures

    Future实例表示可能已经完成或者待完成的延迟计算.done()返回是否已完成, .result()阻塞返回结果
    Future期待待完成的操作, 可以放入队列, 完成的状态可查询, 计算完成后(或抛出异常)可以获取结果(或异常)
    """
    with futures.ThreadPoolExecutor(max_workers=3) as executor:
        to_do = []
        for cc in sorted(cc_list):
            # 排定可调用对象执行时间, 返回一个future, 表示待执行的操作
            future = executor.submit(download_one, cc)
            to_do.append(future)
            msg = 'Scheduled for {}: {}'
            print(msg.format(cc, future))
        results = []

        # 函数在future运行结束产出future结果
        for future in futures.as_completed(to_do):
            # 获取future结果
            res = future.result()
            msg = '{} result: {!r}'
            print(msg.format(future, res))
            results.append(res)
    return len(results)



def display(*args):
    print(strftime('[%H:%M:%S]'), end=' ')
    print(*args)


def loiter(n):
    msg = '{} loiter({}): doing nothing for {}s'
    display(msg.format('\t'*n, n, n))
    sleep(n)
    msg = '{}loiter({}): done'
    display(msg.format('\t'* n , n))
    return n * 10

def main():
    display('Script starting.')
    executor = futures.ThreadPoolExecutor(max_workers=3)
    results = executor.map(loiter, range(5))
    display('results:', results)
    display('waiting for individual results:')
    # enumerate函数会隐式调用next(results), 此函数又会在内部表示第一个任务loiter(0)上的future
    # _f.result()方法, 会阻塞, 每次循环都会阻塞下
    for i, result in enumerate(results):
        display('result {}: {}'.format(i, result))